package net.atomcode.bearing.geocoding;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Task for geocoding a supplied query into latitude and longitude elements
*/
public class ReverseGeocodingTask extends GeocodingTask<Double>
{
/**
* Reverse geocode the supplied request using the devices current locale
* @param context The current app context
*/
public ReverseGeocodingTask(Context context, Double[]latlng)
{
super(context, latlng);
}
/**
* Reverse geocode the supplied request using the given explicit locale
* @param locale The locale to use when geocoding the query
*/
public ReverseGeocodingTask(Context context, Double[] latlng, Locale locale)
{
super(context, latlng, locale);
}
@Override protected List<Address> doInBackground(Double... params)
{
if (params == null || params.length < 2)
{
Log.w("Bearing", "Invalid lat,lng supplied to ReverseGeocoder");
return null;
}
Double lat = params[0];
Double lng = params[1];
if (deviceHasNativeGeocoding())
{
return addressForNativeGeocodedQuery(lat, lng);
}
else
{
return addressForRemoteGeocodedQuery(lat, lng);
}
}
/**
* Geocode the query natively and return the result.
*
* Note
* =====
* Some devices, namely Amazon kindles, will report native geocoding support but
* actually not support it. This is caught by a null response. If this occurs
* the fallback {@code addressForRemoteGeocodedQuery} will be called
*
* @param latitude The latitiude of the location to reverse geocode
* @param longitude The longitude of the location to reverse geocode
* @return The geocoded location
*/
private List<Address> addressForNativeGeocodedQuery(Double latitude, Double longitude)
{
Geocoder geocoder = new Geocoder(context, locale);
try
{
List<Address> results = geocoder.getFromLocation(latitude, longitude, resultCount);
if (results != null && results.size() > 0)
{
return results;
}
}
catch (IOException ex)
{
return addressForRemoteGeocodedQuery(latitude, longitude);
}
return null;
}
/**
* A fallback alternative that will use a web request to geocode the query.
*
* @param latitude The latitiude of the location to reverse geocode
* @param longitude The longitude of the location to reverse geocode
* @return The geocoded location as returned from the web service.
*/
private List<Address> addressForRemoteGeocodedQuery(Double latitude, Double longitude)
{
StringBuilder data = new StringBuilder();
try
{
HttpClient client = new DefaultHttpClient();
String params = "?latlng=" + latitude + "," + longitude + "&sensor=false";
HttpGet request = new HttpGet(WEB_API_URL + params);
HttpResponse response;
try
{
response = client.execute(request);
}
catch (ClientProtocolException ex)
{
ex.printStackTrace();
return null;
}
InputStream content = response.getEntity().getContent();
InputStreamReader inputStreamReader = new InputStreamReader(content);
BufferedReader reader = new BufferedReader(inputStreamReader);
String line;
while ((line = reader.readLine()) != null)
{
data.append(line);
}
}
catch (IOException ex)
{
Log.e("Bearing", "Network error connecting to Google Geocoding API" + ex.getMessage());
return null;
}
try
{
/*
{
"results": [
{
"geometry": {
"location": {
"lat": <latitude>
"lng": <longitude>
}
}
}
]
}
*/
JSONObject geocodeData = new JSONObject(data.toString());
JSONArray addresses = geocodeData.getJSONArray("results");
int resultsToRead = Math.min(resultCount, addresses.length());
List<Address> addressList = new ArrayList<Address>(resultsToRead);
for (int i = 0; i < resultsToRead; i++)
{
JSONObject firstResult = addresses.getJSONObject(0);
JSONObject geometry = firstResult.getJSONObject("geometry");
JSONObject locationData = geometry.getJSONObject("location");
Address result = new Address(locale);
result.setLatitude(locationData.getDouble("lat"));
result.setLongitude(locationData.getDouble("lng"));
JSONArray addressData = firstResult.getJSONArray("address_components");
for (int addressIndex = 0; addressIndex < addressData.length(); addressIndex++)
{
JSONObject addressLine = addressData.getJSONObject(addressIndex);
String addressLineString = addressLine.getString("long_name");
result.setAddressLine(addressIndex, addressLineString);
JSONArray types = addressLine.getJSONArray("types");
for (int typeIter = 0; typeIter < types.length(); typeIter++)
{
String type = types.getString(typeIter);
if (type.equals("street_number"))
{
result.setPremises(addressLineString);
}
else if (type.equals("route"))
{
result.setSubThoroughfare(addressLineString);
}
else if (type.equals("neighborhood"))
{
result.setThoroughfare(addressLineString);
}
else if (type.equals("sublocality"))
{
result.setSubLocality(addressLineString);
}
else if (type.equals("administrative_area_level_2"))
{
result.setSubAdminArea(addressLineString);
}
else if (type.equals("administrative_area_level_1"))
{
result.setAdminArea(addressLineString);
}
else if (type.equals("country"))
{
result.setCountryName(addressLineString);
result.setCountryCode(addressLine.getString("short_name"));
}
else if (type.equals("postal_code"))
{
result.setPostalCode(addressLineString);
}
}
}
addressList.add(result);
}
return addressList;
}
catch (JSONException ex)
{
Log.e("Bearing", "Google Geocoding API format parsing failed! " + ex.getMessage());
}
return null;
}
}